package log

import (
	"fmt"
	"path"
	"strings"
	"time"

	"sieve_engine/lib/config"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var (
	logConfig = LogConfig{
		Name:       *config.String("zaplog.name", "sieve_engine", "zap log name"),
		Enable:     *config.Bool("zaplog.enable", true, "zap log enable"),
		Level:      *config.String("zaplog.level", "", "zap log level"),
		Prefix:     *config.String("zaplog.prefix", "", "zap log prefix"),
		Fields:     *config.String("zaplog.fields", "", "zap log fields"),
		Path:       *config.String("zaplog.path", "", "zap log path"),
		MaxSize:    *config.Int("zaplog.max_size", 5, "zap log max_size"),
		MaxBackups: *config.Int("zaplog.max_backups", 3, "zap log max_backups"),
		MaxAge:     *config.Int("zaplog.max_age", 3, "zap log max_age"),
		Compress:   *config.Bool("zaplog.compress", false, "zap log compress"),
	}
)

type LogConfig struct {
	Name       string `json:"name"  mapstructure:"name"`
	Enable     bool   `json:"enable" mapstructure:"enable"`
	Level      string `json:"level"  mapstructure:"level"`
	Prefix     string `json:"prefix" mapstructure:"prefix"`
	Fields     string `json:"fields" mapstructure:"fields"`
	Path       string `json:"path" mapstructure:"path"`
	MaxSize    int    `json:"maxSize" mapstructure:"max_size"`
	MaxBackups int    `json:"maxBackups" mapstructure:"max_backups"`
	MaxAge     int    `json:"maxAge" mapstructure:"max_age"`
	Compress   bool   `json:"compress" mapstructure:"compress"`
}

func CreateLogger(flushChan chan int, prefix string) *zap.Logger {

	var logger *zap.Logger

	go func() {
		select {
		case <-flushChan:
			logger.Info(fmt.Sprintf("application exit,%s flush log to persistent file", prefix))
			_ = logger.Sync()
		}
	}()

	var cfg zap.Config
	zap.NewDevelopmentConfig()
	var level zap.AtomicLevel
	if logConfig.Level == "" {
		level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
	} else {
		var tlevel zapcore.Level
		switch logConfig.Level {
		case "debug":
			tlevel = zapcore.DebugLevel
		case "info":
			tlevel = zapcore.InfoLevel
		case "warn":
			tlevel = zapcore.WarnLevel
		case "error":
			tlevel = zapcore.ErrorLevel
		case "dpanic":
			tlevel = zapcore.DPanicLevel
		case "panic":
			tlevel = zapcore.PanicLevel
		case "fatal":
			tlevel = zapcore.FatalLevel
		default:
			tlevel = zapcore.InfoLevel
		}
		level = zap.NewAtomicLevelAt(tlevel)
	}
	cfg.Level = level
	cfg.Encoding = "json"
	cfg.OutputPaths = []string{"stdout"}
	cfg.ErrorOutputPaths = []string{"stderr"}
	cfg.DisableCaller = false
	cfg.DisableStacktrace = false

	fields := make(map[string]interface{})

	if logConfig.Fields != "" {
		parts := strings.Split(logConfig.Fields, ",")
		for _, p := range parts {
			tp := strings.Split(p, "=")
			fields[tp[0]] = tp[1]
		}
	}

	cfg.InitialFields = fields

	cfg.Sampling = nil

	cfg.EncoderConfig = zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     EpochTimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	ll := &lumberjack.Logger{
		Filename:   path.Join(logConfig.Path, fmt.Sprintf("%s_%s", logConfig.Name, prefix)+".log"),
		MaxSize:    logConfig.MaxSize, // megabytes
		MaxBackups: logConfig.MaxBackups,
		MaxAge:     logConfig.MaxAge,   // days
		Compress:   logConfig.Compress, // disabled by default
	}

	// logFile, _ := os.OpenFile(, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777)
	fileWriter := zapcore.AddSync(ll)

	jsonEncoder := zapcore.NewJSONEncoder(cfg.EncoderConfig)

	core := zapcore.NewTee(zapcore.NewCore(jsonEncoder, fileWriter, cfg.Level))

	var fieldOptions []zap.Field
	if len(cfg.InitialFields) > 0 {
		for k, v := range cfg.InitialFields {
			fieldOptions = append(fieldOptions, zap.Field{Key: k, String: v.(string), Type: zapcore.StringType})
		}
	}

	logger = zap.New(core, zap.AddCaller(), zap.Development(), zap.Fields(fieldOptions...))
	return logger
}

func EpochTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	format := t.Format("2006-01-02 15:04:05")
	enc.AppendString(format)
}
